﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace System.Management.HyperV
{
    /// <summary>
    /// Represents the virtualization service present on a single host system. Msvm_VirtualSystemManagementService is used to control the definition, modification, and deletion of virtual systems. It also has methods for performing operations on virtual systems, such as cloning, snapshotting, and the importing or exporting of virtual systems.
    /// </summary>
    /// <remarks>http://msdn.microsoft.com/en-us/library/cc136940(VS.85).aspx</remarks>
    public class VirtualSystemManagementService : ManagementServiceBase
    {
        public const string WmiClassName = "Msvm_VirtualSystemManagementService";

        #region Constructor
        public VirtualSystemManagementService()
            : base(VirtualSystemManagementService.WmiClassName)
        {
        }
        #endregion

        #region SnapShots
        /// <summary>
        /// Creates a snapshot of a virtual computer system that is in the "Enabled", "Disabled", or "Suspended" state. The result of the snapshot is a new instance of Msvm_VirtualSystemSettingData representing the settings for the virtual computer system at the time that the snapshot is taken. In addition, a new instance of each CIM_ResourceAllocationSettingData object will also be created to represent the settings for the devices in the virtual system at the time that the snapshot is taken.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136785(VS.85).aspx</remarks>
        public Model.Snapshot CreateVirtualSystemSnapshot(string vmName)
        {
            string methodName = "CreateVirtualSystemSnapshot";

            using (var inParams = this.ManagementServiceInstance.GetMethodParameters(methodName))
            {
                inParams["SourceSystem"] = GetVmByName(vmName).Path.Path;

                var result = InvokeMethod(methodName, inParams, waitForCompleted: true);

                var snapshotSettingData = result.Job.RawJob.GetRelated("Msvm_VirtualSystemsettingData").OfType<ManagementObject>().First();

                return new Model.Snapshot(snapshotSettingData);
            }
        }

        /// <summary>
        /// Applies the disk state, runtime state, and configuration values for a snapshot to the virtual computer system. This information serves as the starting point for the virtual computer system when it is next activated. Any disk state, runtime state, or configuration values currently associated with the virtual computer system will be lost.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136767(VS.85).aspx</remarks>
        /// <param name="vmName"></param>
        /// <param name="snapshotName"></param>
        public void ApplyVirtualSystemSnapshot(string vmName, string snapshotName)
        {
            var targetVM = GetVmByName(vmName);
            var targetSnapshot = GetSnapshotByUniqueName(snapshotName, targetVM);

            string methodName = "ApplyVirtualSystemSnapshot";

            using (var inParams = this.ManagementServiceInstance.GetMethodParameters(methodName))
            {
                inParams["ComputerSystem"] = targetVM;
                inParams["SnapshotSettingData"] = targetSnapshot;

                InvokeMethod(methodName, inParams, waitForCompleted: true);
            }
        }

        /// <summary>
        /// Not Yet Implemented
        /// </summary>
        public void ApplyVirtualSystemSnapshotEx()
        {
            // TODO: if (!IsR2()) throw new NotSupportedException();

            throw new NotImplementedException();
        }

        /// <summary>
        /// Removes the specified snapshot from the virtual computer system. Any configuration or runtime state associated with the snapshot will be lost.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136962(VS.85).aspx</remarks>
        /// <param name="vmName"></param>
        /// <param name="snapshotName"></param>
        public void RemoveVirtualSystemSnapshot(string vmName, string snapshotName)
        {
            var targetVM = GetVmByName(vmName);
            var targetSnapshot = GetSnapshotByUniqueName(snapshotName, targetVM);

            string methodName = "RemoveVirtualSystemSnapshot";

            using (var inParams = this.ManagementServiceInstance.GetMethodParameters(methodName))
            {
                inParams["SnapshotSettingData"] = targetSnapshot;

                InvokeMethod(methodName, inParams, waitForCompleted: true);
            }
        }
        /// <summary>
        /// Removes an existing snapshot and all its children of a virtual system.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc160712(VS.85).aspx</remarks>
        /// <param name="vmName"></param>
        /// <param name="snapshotName"></param>
        public void RemoveVirtualSystemSnapshotTree(string vmName, string snapshotName)
        {
            var targetVM = GetVmByName(vmName);
            var targetSnapshot = GetSnapshotByUniqueName(snapshotName, targetVM);

            string methodName = "RemoveVirtualSystemSnapshotTree";

            using (var inParams = this.ManagementServiceInstance.GetMethodParameters(methodName))
            {
                inParams["SnapshotSettingData"] = targetSnapshot;

                InvokeMethod(methodName, inParams, waitForCompleted: true);
            }
        }

        private static ManagementObject GetSnapshotByUniqueName(string snapshotName, ManagementObject targetVM)
        {
            var snapshotQuery = from mo in targetVM.GetRelated("Msvm_VirtualSystemSettingData").OfType<ManagementObject>()
                                where (SettingType)mo.Properties["SettingType"].Value == SettingType.VirtualSystemSnapshot
                                      &&
                                      (string)mo.Properties["ElementName"].Value == snapshotName
                                select mo;

            var snapshotList = snapshotQuery.ToArray();

            if (snapshotList.Length > 1)
            {
                throw new HyperVException(string.Format("The provided snapshot name '{0}' is not unique. Please use GUID instead.", snapshotName));
            }

            var targetSnapshot = snapshotQuery.First();
            return targetSnapshot;
        }
        #endregion

        #region Virtual System Methods
        /// <summary>
        /// Deletes an existing virtual computer system definition.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136790(VS.85).aspx</remarks>
        public void DestroyVirtualSystem(string vmName)
        {
            var targetVM = GetVmByName(vmName);

            string methodName = "DestroyVirtualSystem";

            using (var inParams = this.ManagementServiceInstance.GetMethodParameters(methodName))
            {
                inParams["ComputerSystem"] = targetVM;

                InvokeMethod(methodName, inParams, waitForCompleted: true);
            }
        }
        /// <summary>
        /// Creates a new virtual computer system instance.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136786(VS.85).aspx</remarks>
        public void DefineVirtualSystem(string vmDisplayName)
        {
            string settingData = GetDefaultVirtualSystemGlobalSettingData(vmDisplayName);

            string methodName = "DefineVirtualSystem";

            using (var inParams = this.ManagementServiceInstance.GetMethodParameters(methodName))
            {
                inParams["ResourceSettingData"] = null;
                inParams["SourceSetting"] = null;
                inParams["SystemSettingData"] = settingData;

                InvokeMethod(methodName, inParams, waitForCompleted: true);

                // TODO: Process and return outParam["DefinedSystem"]
            }
        }

        private string GetDefaultVirtualSystemGlobalSettingData(string vmDisplayName)
        {
            var globalSettingPath = new ManagementPath("Msvm_VirtualSystemGlobalsettingData");
            using (var globalSettingClass = new ManagementClass(this.ManagementServiceInstance.Scope, globalSettingPath, null))
            {
                using (var globalSettingData = globalSettingClass.CreateInstance())
                {
                    globalSettingData["ElementName"] = vmDisplayName;

                    return globalSettingData.GetText(TextFormat.CimDtd20);
                }
            }
        }
        #endregion

        #region Export / Import Virtual System
        /// <summary>
        /// Exports a virtual computer system, or a snapshot of a virtual computer system, in the "powered off" or "saved" state to a file. The virtual computer system, its associated configuration settings, and its associated resource settings will be preserved in the resulting file.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/dd379580(VS.85).aspx</remarks>
        /// <param name="exportDirectory">The fully-qualified path of the directory to which the virtual computer system is to be exported. If the CreateVmExportSubdirectory property in the ExportSettingData parameter is set to True then this directory can be reused for exporting multiple virtual machines and this method places each virtual computer system definition in a separate subdirectory under this path.</param>
        /// <param name="exportSettingsData">An instance of the Msvm_VirtualSystemExportSettingData class that represents the virtual computer system.</param>
        public void ExportVirtualSystemEx(string vmName, string exportDirectory, string exportSettingsData)
        {
            // TODO: Support snapshots.
            // TODO: Verify state is "powered off" or "saved".

            var vm = GetVmByName(vmName);

            string methodName = "ExportVirtualSystemEx";
            using (var inParams = this.ManagementServiceInstance.GetMethodParameters(methodName))
            {
                inParams["ComputerSystem"] = vm;
                inParams["ExportDirectory"] = exportDirectory;
                inParams["ExportSettingData"] = exportSettingsData;

                InvokeMethod(methodName, inParams, waitForCompleted: true);
            }
        }

        /// <summary>
        /// Returns an instance of the Msvm_VirtualSystemImportSettingData class.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/dd379581(VS.85).aspx</remarks>
        /// <param name="importDirectory">The fully-qualified path to the directory that represents a previously exported virtual computer system. This file must have been generated by a previous call to the ExportVirtualSystem or ExportVirtualSystemEx methods.</param>
        /// <returns>Represents settings of a virtual system to import. Used by the ImportVirtualSystemEx method of the Msvm_VirtualSystemManagementService class.</returns>
        public ImportSettingData GetVirtualSystemImportSettingData(string importDirectory)
        {
            string methodName = "GetVirtualSystemImportSettingData";

            using (var inParams = this.ManagementServiceInstance.GetMethodParameters(methodName))
            {
                inParams["ImportDirectory"] = importDirectory;

                var result = InvokeMethod(methodName, inParams, waitForCompleted: true);

                return new ImportSettingData(result.Job.RawJob.Properties["ImportSettingData"]);
            }
        }

        /// <summary>
        /// Imports a virtual computer system definition from a file. The resulting virtual computer system will be in the "Disabled" state.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/dd379583(VS.85).aspx</remarks>
        /// <param name="importDirectory">The fully-qualified path to the directory that represents a previously exported virtual computer system. This file must have been generated by a previous call to the ExportVirtualSystem or ExportVirtualSystemEx method.</param>
        /// <param name="isd">An instance of the Msvm_VirtualSystemImportSettingData class returned from the GetVirtualSystemImportSettingData method that represents the virtual computer system to be imported.</param>
        public void ImportVirtualSystemEx(string importDirectory, ImportSettingData isd)
        {
            throw new NotImplementedException();
        }
        #endregion

        #region Queries
        internal ManagementObject GetVmByName(string vmName)
        {
            var query = new ObjectQuery(string.Format("SELECT * FROM Msvm_ComputerSystem WHERE ElementName = '{0}'", vmName));

            using (var searcher = new ManagementObjectSearcher(this.ManagementServiceInstance.Scope, query))
            {
                var searchResults = searcher.Get().OfType<ManagementObject>();

                if (searchResults.Count() == 0) throw new HyperVException("Virtual Machine name not found.");
                if (searchResults.Count() > 1) throw new HyperVException("Encountered duplicate ElementName in GetVmByName query results.");

                return searchResults.First();
            }
        }

        public void SetScope(string vmName, string scopeName)
        {
            var moVM = GetVmByName(vmName);
            var moVmGlobalSettings = moVM.GetRelated("MSVM_VirtualSystemGlobalSettingData", "MSVM_ElementSettingData", null, null, null, null, false, null);
            var moVmGlobalSetting = moVmGlobalSettings.OfType<ManagementObject>().AsQueryable().First();
            
            moVmGlobalSetting.SetPropertyValue("ScopeOfResidence", scopeName);

            string methodName = "ModifyVirtualSystem";

            using (var inParams = this.ManagementServiceInstance.GetMethodParameters(methodName))
            {
                inParams["ComputerSystem"] = moVM;
                inParams["SystemSettingData"] = moVmGlobalSetting.GetText(TextFormat.CimDtd20);

                var result = InvokeMethod(methodName, inParams, waitForCompleted: true);

                var dbgModifiedSettingData = result.OutParameters["ModifiedSettingData"];
                var dbgJob = result.Job;
            }
        }

        public string GetScope(string vmName)
        {
            var query = new ObjectQuery(string.Format("SELECT * FROM MSVM_VirtualSystemGlobalSettingData WHERE ElementName = '{0}'", vmName));

            using (var searcher = new ManagementObjectSearcher(this.ManagementServiceInstance.Scope, query))
            {
                var searchResults = searcher.Get().OfType<ManagementObject>();

                if (searchResults.Count() == 0) throw new HyperVException("Virtual Machine name not found.");
                if (searchResults.Count() > 1) throw new HyperVException("Encountered duplicate ElementName in GetVmByName query results.");

                return searchResults.First().GetPropertyValue("ScopeOfResidence").ToString();
            }

        }

        public string[] GetVmList()
        {
            var query = new ObjectQuery("SELECT * FROM Msvm_ComputerSystem");

            using (var searcher = new ManagementObjectSearcher(this.ManagementServiceInstance.Scope, query))
            {
                var results = from mo in searcher.Get().OfType<ManagementObject>()
                              select (string)mo.Properties["ElementName"].Value;

                return results.ToArray();
            }
        }
        #endregion

        #region Not Supported
        /// <summary>
        /// This method is not supported.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136940(VS.85).aspx#methods</remarks>
        public void RequestStateChange() { throw new NotSupportedException(); }
        /// <summary>
        /// This method is not supported.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136940(VS.85).aspx#methods</remarks>
        public void StartService() { throw new NotSupportedException(); }
        /// <summary>
        /// This method is not supported.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136940(VS.85).aspx#methods</remarks>
        public void StopService() { throw new NotSupportedException(); }
        /// <summary>
        /// This method is not supported.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136940(VS.85).aspx#methods</remarks>
        public void PlanVirtualSystem() { throw new NotSupportedException(); }
        /// <summary>
        /// This method is not supported.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136940(VS.85).aspx#methods</remarks>
        public void InstantiateVirtualSystem() { throw new NotSupportedException(); }
        /// <summary>
        /// This method is not supported.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136940(VS.85).aspx#methods</remarks>
        public void CloneVirtualSystem() { throw new NotSupportedException(); }
        #endregion

        #region Not Implemented
        public void ModifyVirtualSystem() { throw new NotImplementedException(); }
        public void ModifyVirtualSystemResources() { throw new NotImplementedException(); }
        public void ImportVirtualSystemEx() { throw new NotImplementedException(); }
        public void AddVirtualSystemResources() { throw new NotImplementedException(); }
        public void RemoveVirtualSystemResources() { throw new NotImplementedException(); }
        public void GetVirtualSystemThumbnailImage() { throw new NotImplementedException(); }
        public void ModifyServiceSettings() { throw new NotImplementedException(); }
        public void GetSummaryInformation() { throw new NotImplementedException(); }
        public void AddKvpItems() { throw new NotImplementedException(); }
        public void ModifyKvpItems() { throw new NotImplementedException(); }
        public void RemoveKvpItems() { throw new NotImplementedException(); }
        public void FormatError() { throw new NotImplementedException(); }
        public void GetSystemCompatibilityInfo() { throw new NotImplementedException(); }
        public void CheckSystemCompatibilityInfo() { throw new NotImplementedException(); }
        /// <summary>
        /// Beginning with Windows Server 2008 R2 this method is deprecated. Use the ExportVirtualSystemEx method.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136793(VS.85).aspx</remarks>
        public void ExportVirtualSystem() { throw new NotImplementedException(); }
        /// <summary>
        /// Beginning with Windows Server 2008 R2 this method is deprecated. Use the ImportVirtualSystemEx method.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136798(VS.85).aspx</remarks>
        public void ImportVirtualSystem() { throw new NotImplementedException(); }
        #endregion

        public Model.Host GetHost()
        {
            var query = new ObjectQuery("SELECT * FROM Msvm_ComputerSystem WHERE Description = 'Microsoft Hosting Computer System' ");

            using (var searcher = new ManagementObjectSearcher(this.ManagementServiceInstance.Scope, query))
            {
                var searchResults = from mo in searcher.Get().OfType<ManagementObject>()
                                    select new Model.Host(mo);

                Debug.Assert(searchResults.Count() == 1);

                return searchResults.First();
            }
        }
    }
}
